package com.me.seckill.service.impl;

import com.me.seckill.config.BusinessException;
import com.me.seckill.config.RabbitMQConfig;
import com.me.seckill.constant.RedisKeysConstant;
import com.me.seckill.enums.ErrorCodeEnum;
import com.me.seckill.mapper.StockOrderMapper;
import com.me.seckill.pojo.Stock;
import com.me.seckill.pojo.StockOrder;
import com.me.seckill.service.OrderService;
import com.me.seckill.service.StockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.UUID;

/**
 * Function:
 *
 * @author crossoverJie
 *         Date: 01/05/2018 14:10
 * @since JDK 1.8
 */
@Transactional(rollbackFor = Exception.class)
@Service(value = "DBOrderService")
public class OrderServiceImpl implements OrderService {

    private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);

    /**
     * 原子减至0
     */
    private static final String LUA_SCRIPT_DECRBY_UNTIL_TO_0 = "local current = redis.call('get', KEYS[1]);" +
            "if current == false then " +
            "    return nil;" +
            "end " +
            "if tonumber(ARGV[1]) > tonumber(current) then " +
            "    return redis.call('decrby', KEYS[1], current);" +
            "else " +
            "    return redis.call('decrby', KEYS[1], ARGV[1]);" +
            "end";

    @Resource(name = "DBStockService")
    private StockService stockService;

    @Autowired
    private StockOrderMapper orderMapper;

    @Autowired
    private RedisTemplate<String,String> redisTemplate ;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public int deleteByStockId(int sid) {
        return orderMapper.deleteByStockId(sid);
    }

    @Override
    public int createWrongOrder(int sid) {
        //校验库存
        Stock stock = checkStock(sid);

        //扣库存
        saleStock(stock);

        //创建订单
        return createOrder(stock);
    }

    @Override
    public int createOptimisticOrder(int sid) {
        // 校验库存
        Stock stock = checkStock(sid);

        // 乐观锁更新库存
        saleStockOptimistic(stock);

        // 创建订单
        return createOrder(stock);
    }

    @Override
    public int createOptimisticOrderUseRedis(int sid) {
        // 检验库存，从 Redis 获取
        Stock stock = checkStockByRedis(sid);

        // 乐观锁更新库存 以及更新 Redis
        saleStockOptimisticByRedis(stock);

        // 创建订单
        return createOrder(stock);
    }

    @Override
    public void createOptimisticOrderUseRedisAndMQ(int sid) {
        // 检验库存，从 Redis 获取
        Stock stock = checkStockByRedis(sid);
        incrRedisStock(stock.getId());

        // 交换机 + routingKey 就等于匹配队列
        // CorrelationData 设置消息的唯一性，如：全局订单id
        this.rabbitTemplate.convertAndSend(RabbitMQConfig.SECKILL_TOPIC_EXCHANGE, "seckill.1", stock,
                new CorrelationData(UUID.randomUUID().toString()));

        logger.info("send RabbitMQ success");
    }

    /**
     * Redis 检查库存
     *
     * @param sid 商品id
     */
    private Stock checkStockByRedis(int sid) {
        Integer count = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_COUNT + sid));
        Integer sale = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_SALE + sid));
        if (count.equals(sale)){
            // throw new RuntimeException("库存不足 Redis currentCount=" + sale);
            throw BusinessException.build(ErrorCodeEnum.GL10501, sid);
        }
        Integer version = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_VERSION + sid));
        Stock stock = new Stock();
        stock.setId(sid);
        stock.setCount(count);
        stock.setSale(sale);
        stock.setVersion(version);

        return stock;
    }

    /**
     * 乐观锁更新数据库 还要更新 Redis
     * @param stock
     */
    private void saleStockOptimisticByRedis(Stock stock) {
        int count = stockService.updateStockByOptimistic(stock);
        if (count == 0){
            throw new RuntimeException("并发更新库存失败") ;
        }

        incrRedisStock(stock.getId());
    }

    /**
     * 原子更新 Redis库存
     *
     * @param sid 库存id
     */
    private void incrRedisStock(Integer sid) {
        //自增
        redisTemplate.opsForValue().increment(RedisKeysConstant.STOCK_SALE + sid, 1) ;
        redisTemplate.opsForValue().increment(RedisKeysConstant.STOCK_VERSION + sid, 1) ;
    }

    private Stock checkStock(int sid) {
        Stock stock = stockService.getStockById(sid);
        if (stock.getSale().equals(stock.getCount())) {
            throw new RuntimeException("库存不足");
        }
        return stock;
    }

    /**
     * 乐观锁更新库存
     * @param stock 库存
     */
    private void saleStockOptimistic(Stock stock) {
        int count = stockService.updateStockByOptimistic(stock);
        if (count == 0){
            throw new RuntimeException("并发更新库存失败") ;
        }
    }

    /**
     * 创建订单，并返回 订单id
     * @param stock 秒杀商品信息
     * @return  订单id
     */
    private int createOrder(Stock stock) {
        StockOrder order = new StockOrder();
        order.setSid(stock.getId());
        order.setName(stock.getName());
        int count = orderMapper.insertSelective(order);
        // 插入完成后 mybatis 会赋值 id
        return order.getId();
    }

    private int saleStock(Stock stock) {
        stock.setSale(stock.getSale() + 1);
        return stockService.updateStockById(stock);
    }
}
